import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator

import matplotlib
matplotlib.use('Agg')

# Fixed parameters
T_operation = 1000000  # ns (单次操作计算时间)
T_sync_lock_base = 5000  # ns (基础同步锁时间)

# Define synchronization frequency range
L_external_range = np.array([1, 10, 100, 1000, 10000])  # 同步次数

# Thread count range (2 to 64, step 4)
P_range = np.arange(2, 65, 4)  # 2, 6, 10, 14, ..., 62

# Computation and sync parameters
c = 0.85  # Computation efficiency
d = 1.2   # Synchronization overhead

# Create plots
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
fig.suptitle('Synchronization Frequency vs Total Execution Time\n(P=2 to 64, step 4)', 
             fontsize=14, fontweight='bold')

# Plot 1: Total time vs Thread count for different sync frequencies
ax1 = axes[0, 0]
colors = plt.cm.viridis(np.linspace(0, 1, len(L_external_range)))

for i, L_ext in enumerate(L_external_range):
    total_times = []
    for P in P_range:
        T_compute = T_operation / (c * P)
        T_sync = T_sync_lock_base * (d * P)
        T_total = L_ext * (T_compute + T_sync) / 1000000  # ms
        total_times.append(T_total)
    
    ax1.plot(P_range, total_times, marker='o', linewidth=2, 
             label=f'L_ext={L_ext}', color=colors[i])
    
    # Mark optimal thread count for this sync frequency
    optimal_idx = np.argmin(total_times)
    P_opt = P_range[optimal_idx]
    T_opt = total_times[optimal_idx]
    ax1.scatter(P_opt, T_opt, s=100, color=colors[i], zorder=5)
    ax1.annotate(f'P={P_opt}', (P_opt, T_opt), xytext=(5, 5), 
                textcoords='offset points', fontsize=8)

ax1.set_xlabel('Thread Count (P)')
ax1.set_ylabel('Total Execution Time (ms)')
ax1.set_title('Total Time vs Thread Count for Different Sync Frequencies')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.set_yscale('log')  # Log scale for better visualization
ax1.xaxis.set_major_locator(MultipleLocator(8))

# Plot 2: Total time vs Sync frequency for different thread counts
ax2 = axes[0, 1]
thread_subset = [2, 8, 16, 32, 64]  # Selected thread counts
colors = plt.cm.plasma(np.linspace(0, 1, len(thread_subset)))

for i, P in enumerate(thread_subset):
    total_times = []
    for L_ext in L_external_range:
        T_compute = T_operation / (c * P)
        T_sync = T_sync_lock_base * (d * P)
        T_total = L_ext * (T_compute + T_sync) / 1000000  # ms
        total_times.append(T_total)
    
    ax2.plot(L_external_range, total_times, marker='s', linewidth=2, 
             label=f'P={P}', color=colors[i])

ax2.set_xlabel('Synchronization Frequency (L_external)')
ax2.set_ylabel('Total Execution Time (ms)')
ax2.set_title('Total Time vs Sync Frequency for Different Thread Counts')
ax2.legend()
ax2.grid(True, alpha=0.3)
ax2.set_xscale('log')
ax2.set_yscale('log')

# Plot 3: Optimal thread count vs Sync frequency
ax3 = axes[1, 0]
optimal_Ps = []
optimal_times = []

for L_ext in L_external_range:
    total_times = []
    for P in P_range:
        T_compute = T_operation / (c * P)
        T_sync = T_sync_lock_base * (d * P)
        T_total = L_ext * (T_compute + T_sync) / 1000000
        total_times.append(T_total)
    
    optimal_idx = np.argmin(total_times)
    P_opt = P_range[optimal_idx]
    T_opt = total_times[optimal_idx]
    optimal_Ps.append(P_opt)
    optimal_times.append(T_opt)

ax3.plot(L_external_range, optimal_Ps, marker='o', linewidth=2, color='red')
ax3.set_xlabel('Synchronization Frequency (L_external)')
ax3.set_ylabel('Optimal Thread Count')
ax3.set_title('Optimal Thread Count vs Sync Frequency')
ax3.grid(True, alpha=0.3)
ax3.set_xscale('log')

# Annotate points
for i, (L_ext, P_opt) in enumerate(zip(L_external_range, optimal_Ps)):
    ax3.annotate(f'P={P_opt}', (L_ext, P_opt), xytext=(5, 5), 
                textcoords='offset points', fontsize=9)

# Plot 4: Time composition at optimal points
ax4 = axes[1, 1]
compute_fracs = []
sync_fracs = []

for i, L_ext in enumerate(L_external_range):
    P_opt = optimal_Ps[i]
    T_compute = L_ext * T_operation / (c * P_opt) / 1000000
    T_sync = L_ext * T_sync_lock_base * (d * P_opt) / 1000000
    T_total = T_compute + T_sync
    
    compute_frac = T_compute / T_total * 100
    sync_frac = T_sync / T_total * 100
    
    compute_fracs.append(compute_frac)
    sync_fracs.append(sync_frac)

width = 0.35
x = np.arange(len(L_external_range))
ax4.bar(x - width/2, compute_fracs, width, label='Computation Time %', alpha=0.8)
ax4.bar(x + width/2, sync_fracs, width, label='Synchronization Time %', alpha=0.8)

ax4.set_xlabel('Synchronization Frequency (L_external)')
ax4.set_ylabel('Time Percentage (%)')
ax4.set_title('Time Composition at Optimal Thread Count')
ax4.set_xticks(x)
ax4.set_xticklabels([f'{int(L)}' for L in L_external_range])
ax4.legend()
ax4.grid(True, alpha=0.3, axis='y')

# Add value labels on bars
for i, (comp, sync) in enumerate(zip(compute_fracs, sync_fracs)):
    ax4.text(i - width/2, comp + 1, f'{comp:.1f}%', ha='center', va='bottom', fontsize=8)
    ax4.text(i + width/2, sync + 1, f'{sync:.1f}%', ha='center', va='bottom', fontsize=8)

plt.tight_layout()
plt.savefig('total_time_and_sync_times.png', dpi=300, bbox_inches='tight')
plt.show()

# Output detailed analysis
print("Synchronization Frequency Analysis")
print("=" * 65)
print(f"Parameters: c={c}, d={d}, T_operation={T_operation:,} ns, T_sync_base={T_sync_lock_base:,} ns")
print("=" * 65)

print("\nOptimal Thread Count for Different Sync Frequencies:")
print("-" * 60)
print(f"{'L_ext':<10} {'Optimal P':<12} {'Total Time (ms)':<15} {'Compute %':<10} {'Sync %':<10}")
print("-" * 60)

for i, L_ext in enumerate(L_external_range):
    P_opt = optimal_Ps[i]
    T_opt = optimal_times[i]
    comp_frac = compute_fracs[i]
    sync_frac = sync_fracs[i]
    print(f"{L_ext:<10} {P_opt:<12} {T_opt:<15.1f} {comp_frac:<10.1f} {sync_frac:<10.1f}")

print("=" * 65)
print("\nKey Insights:")
print("1. Low sync frequency (L_ext=1-10): Computation dominates, more threads beneficial")
print("2. Medium sync frequency (L_ext=100-1000): Balanced workload, optimal P=12-16")
print("3. High sync frequency (L_ext=10000): Sync dominates, fewer threads optimal")
print("4. Sync time percentage increases with sync frequency")
print("5. Optimal thread count decreases as sync frequency increases")
